package ua.edu.nuos.jeetraining2012.cms.model;

import org.slf4j.Logger;
import ua.edu.nuos.jeetraining2012.cms.dto.HomeWorkDTO;
import ua.edu.nuos.jeetraining2012.cms.dto.HomeWorkDetailedDTO;
import ua.edu.nuos.jeetraining2012.cms.dto.UpcomingHomeWorkDTO;
import ua.edu.nuos.jeetraining2012.cms.entity.HomeWorkEntity;
import ua.edu.nuos.jeetraining2012.cms.model.exception.DAOException;

import javax.ejb.Local;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import java.sql.Date;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * @author Victor N. Ganichev victor.ganichev@gmail.com
 * @version 1.0 Date: 24.06.13 Time: 11:28
 * @since 1.0
 */
@Local
@Stateless
public class HomeWorkDAO {

    /**
     * Define entity manager
     */
    @PersistenceContext
    private EntityManager em;

    /**
     * Define logger
     */
    final private Logger logger = org.slf4j.LoggerFactory.getLogger(HomeWorkDAO.class);

    // Constructors.

    /**
     * Default constructor.
     */
    public HomeWorkDAO() {
        logger.debug("HomeWorkDAO invocation.");
    }

    /**
     * Constructor with predefined EntityManager, use this one for testing purposes.
     *
     * @param em        Instance of EntityManager.
     */
    public HomeWorkDAO(EntityManager em) {
        logger.debug("HomeWorkDAO invocation with predefined EntityManager.");
        setEm(em);
    }

    // Getters & Setters
    protected EntityManager getEm() {
        return em;
    }

    private void setEm(EntityManager em) {
        this.em = em;
    }

    /**
     * Find all homework, available for material with id <code>materialId</code>.
     * <p/>
     * In case of success, return <code>List&lt;HomeWorkEntity&gt;HomeWorkEntity</code> or null.
     * <p/>
     * If materialId is <code>null</code> or less than 1, IllegalArgumentException will be thrown.<br/> If any parent
     * exception appeared, {@link DAOException} will be thrown.<br/>
     *
     * @param materialId Material ID.
     * @return List of homework, available for this material.
     * @throws IllegalArgumentException If materialId is null or less than 1.
     * @throws DAOException             If parent exception appeared.
     */
    @SuppressWarnings("unchecked")
    public List<HomeWorkEntity> findHomeworkByMaterialId(Long materialId) throws IllegalArgumentException, DAOException {
        logger.debug("findHomeworkByMaterialId() - started.");

        if (materialId == null || materialId < 1) {
            logger.error("Material ID can't be {}", materialId);
            logger.debug("findHomeworkByMaterialId() - finished with IllegalArgumentException.");
            throw new IllegalArgumentException("Material ID can't be null or less than 1.");
        }

        logger.debug("Trying to find homework for material with ID {}", materialId);
        List<HomeWorkEntity> homework = null;
        try {
            final String sql = "SELECT * FROM homeworks WHERE material_id = ?1";
            Query q = em.createNativeQuery(sql,HomeWorkEntity.class).setParameter(1, materialId);
            logger.debug("Finding with query: {}", q);

            homework = q.getResultList();
            if (homework != null) {
                logger.info("Found {} homework for this course", homework.size());
            }
        } catch (Exception e) {
            logger.error("Can't find any homework due error {}. \nTrace:\n {}", e.getMessage(),
                    e.getStackTrace());
            logger.debug("findHomeworkByMaterialId() - finished with DAOException.");
            throw new DAOException("Can't find any homework due error: " + e.getMessage());
        }

        logger.debug("findHomeworkByMaterialId() - finished.");
        return homework;
    }

    /**
     * Return homework list by course ID.
     * <p/>
     * In case of success, List of {@link HomeWorkDTO} will be returned or empty ArrayList if nothing were found.
     * <p/>
     * If courseId is null or less than 1, IllegalArgumentException will be thrown.
     *
     * @param courseId                      Course ID.
     * @return                              List of HomeWorkDTOs or empty list.
     * @throws IllegalArgumentException     If courseId is null or less than 1.
     * @throws DAOException                 If any parent exception appeared.
     */
    @SuppressWarnings("unchecked")
    public List<HomeWorkDTO> findHomeWorkListByCourseId(Long courseId) throws IllegalArgumentException, DAOException {
        logger.debug("findHomeWorkByCourseId() - started.");

        if (courseId == null || courseId < 1) {
            logger.error("Course ID can't be {}", courseId);
            logger.debug("findHomeWorkByCourseId() - finished with IllegalArgumentException.");
            throw new IllegalArgumentException("Course ID can't be null or less than 1.");
        }

        logger.debug("Trying to find homework list for course with ID {}", courseId);
        List<HomeWorkDTO> hwList = null;
        try {
            final String sql = "SELECT hw.id, DATE(hw.due_date), hw.material_id, m.title, hw.title " +
                    "FROM homeworks hw " +
                    "INNER JOIN materials m ON (hw.material_id = m.id) " +
                    "INNER JOIN courses c ON (c.id = m.course_id) " +
                    "WHERE c.id = ?1 " +
                    "ORDER BY hw.material_id, hw.due_date";
            Query q = em.createNativeQuery(sql).setParameter(1, courseId);

            logger.debug("Querying with request {}", q);
            List<Object[]> resultList = q.getResultList();
            if (resultList != null && !resultList.isEmpty()) {
                hwList = new ArrayList<>();

                for (Object[] resArray : resultList) {
                    HomeWorkDTO dto = new HomeWorkDTO();
                    dto.setId(Long.valueOf(String.valueOf(resArray[0])));
                    dto.setDueDate(Date.valueOf(String.valueOf(resArray[1])));
                    dto.setMaterialId(Long.valueOf(String.valueOf(resArray[2])));
                    dto.setMaterialTitle(String.valueOf(resArray[3]));
                    dto.setHomeWorkTitle(String.valueOf(resArray[4]));
                    hwList.add(dto);
                }

                logger.debug("Found {} HomeWork items", hwList.size());
            }
        } catch (Exception e) {
            logger.error("Can't find homework because of error {}.\nTrace:\n {}", e.getMessage(),
                    e.getStackTrace());
            logger.debug("findHomeWorkByCourseId() - finished with DAOException.");
            throw new DAOException("Can't find homework. Error message: " + e.getMessage());
        }

        logger.debug("findHomeWorkByCourseId() - finished.");
        return hwList;
    }

    /**
     * Fetch last upcoming homework list for course with ID <code>courseId</code>. Limit this list with
     * <code>limit</code> elements. In case of success, method will return List of HomeWorkEntities.
     * <p/>
     * If limit elements are null or less than 1, DEFAULT_LIMIT elements will be fetched.
     * <p/>
     * If courseId is null or less than 1, IllegalArgumentException will be thrown. If any parent exception appeared,
     * DAOException will be produced.
     *
     * @param courseId Course ID.
     * @param limit    Limit result to.
     * @return List of all upcoming HW, limited to <code>limit</code>.
     * @throws DAOException             If any parent exception appeared.
     * @throws IllegalArgumentException If courseId is null or less than 1.
     */
    @SuppressWarnings("unchecked")
    public List<HomeWorkEntity> findNearestList(Long courseId, Long limit) throws DAOException, IllegalArgumentException {
        logger.debug("findNearest() - started.");
        final Long DEFAULT_LIMIT = 1l;

        if (courseId == null || courseId < 1) {
            logger.error("Course ID can't be {}", courseId);
            logger.debug("findNearest() - finished with IllegalArgumentException.");
            throw new IllegalArgumentException("Course ID can't be null or less than 1");
        }

        if (limit == null || limit < 1) {
            logger.error("Limit can't be {}", limit);
            logger.debug("Using default limit 10");
            limit = DEFAULT_LIMIT;
        }

        logger.debug("Trying to fetch homework for course with ID {} with limit in {} records"
                , courseId, limit);
        List<HomeWorkEntity> homework = null;
        try {
            final String sql = "SELECT hw.due_date " +
                    "FROM homeworks hw " +
                    "INNER JOIN materials m ON (hw.material_id = m.id) " +
                    "INNER JOIN courses c ON (c.id = m.course_id) " +
                    "WHERE c.id = ?1 AND hw.due_date > CURRENT_DATE " +
                    "ORDER BY hw.due_date " +
                    "LIMIT ?2";
            Query q = em.createNativeQuery(sql).setParameter(1, courseId).setParameter(2, limit);
            homework = q.getResultList();
            if (homework != null) {
                logger.info("Found {} homework for this course", homework.size());
            }
        } catch (Exception e) {
            logger.error("Can't find any homework, got error: {}.\nTrace:\n {}", e.getMessage(),
                    e.getStackTrace());
            logger.debug("findNearest() - finished with DAOException.");
            throw new DAOException("Can't find any homework. Error: " + e.getMessage());
        }

        logger.debug("findNearest() - finished.");
        return homework;
    }

    /**
     * Find nearest HW by courseId.
     * <p/>
     * In case of success {@link UpcomingHomeWorkDTO} will be returned or <code>null</code> if no one HW was found.
     * <p/>
     * If course ID is <code>null</code> or less than 1, IllegalArgumentException will be thrown. If any parent
     * exception in EntityManager appeared, DAOException will be thrown.
     *
     * @param courseId Course ID.
     * @return Filled in UpcomingHomeWorkDTO or null.
     * @throws IllegalArgumentException If courseId is null or less than 1.
     * @throws DAOException             If any parent exception appeared.
     */
    @SuppressWarnings("unchecked")
    public UpcomingHomeWorkDTO findNearestByCourseId(Long courseId) throws IllegalArgumentException, DAOException {
        logger.debug("findNearestByCourseId() - started.");

        if (courseId == null || courseId < 1) {
            logger.error("CourseId can't be {}", courseId);
            logger.debug("findNearestByCourseId() - finished with IllegalArgumentException.");
            throw new IllegalArgumentException("CourseId can't be null or less than 1.");
        }

        logger.debug("Trying to find nearest upcoming HW.");
        UpcomingHomeWorkDTO upcomingHW = null;
        try {
            final String sql = "SELECT c.id, c.title, hw.id, DATE(hw.due_date), m.title " +
                    "FROM homeworks hw " +
                    "INNER JOIN materials m ON (hw.material_id = m.id) " +
                    "INNER JOIN courses c ON (c.id = m.course_id) " +
                    "WHERE c.id = ?1 AND hw.due_date > CURRENT_DATE " +
                    "ORDER BY hw.due_date " +
                    "LIMIT 1";
            Query q = em.createNativeQuery(sql).setParameter(1, courseId);
            logger.debug("Querying with request {}", q);
            Object[] result = (Object[]) q.getSingleResult();

            if (result != null) {

                upcomingHW = new UpcomingHomeWorkDTO();
                upcomingHW.setCourseId(Long.valueOf(String.valueOf(result[0])));
                upcomingHW.setCourseTitle(String.valueOf(result[1]));
                upcomingHW.setHwId(Long.valueOf(String.valueOf(result[2])));
                upcomingHW.setHwDueDate(Date.valueOf(String.valueOf(result[3])));
                upcomingHW.setMaterialTitle(String.valueOf(result[4]));

                logger.debug("Found HW with ID {} and due date {}", upcomingHW.getHwId(), upcomingHW.getHwDueDate());
            }
        } catch (NoResultException e) {
            logger.info("Can't find any home work.");
        } catch (Exception e) {
            logger.error("Can't find Homework because of error: {},\nTrace:\n {}", e.getMessage(),
                    e.getStackTrace());
            logger.debug("findNearestByCourseId() - finished with DAOException.");
            throw new DAOException("Homework can't be found. Error message: " + e.getMessage());
        }

        logger.debug("findNearestByCourseId() - finished.");
        return upcomingHW;
    }

    /**
     * Find detailed info about homework.
     * <p/>
     * In case of success, return instance of HomeWorkDetailedDTO or <code>null</code> if nothing was found.
     * <p/>
     * If hwId is <code>null</code> or less than 1, IllegalArgumentException will be thrown.
     * If any parent exception appeared during find operation, {@link DAOException} will be thrown.
     *
     * @param hwId                          Homework ID
     * @return                              {@link HomeWorkDetailedDTO} or <code>null</code>.
     * @throws IllegalArgumentException     If homework ID is null or less than 1.
     * @throws DAOException                 If any parent exception appeared.
     */
    public HomeWorkDetailedDTO findHomeworkById(Long hwId) throws IllegalArgumentException, DAOException {
        logger.debug("findHomeworkById() - started.");

        if (hwId == null || hwId < 1) {
            logger.error("Homework ID can't be {}", hwId);
            logger.debug("findHomeworkById() - finished with IllegalArgumentException.");
            throw new IllegalArgumentException("Homework ID can't be null or less than 1");
        }

        HomeWorkDetailedDTO dto = null;
        logger.debug("Trying to find homework info.");
        try {
            final String sql = "SELECT hw.description, DATE(hw.due_date), hw.files, hw.scores, DATE(hw.start_date), m.course_id, hw.title " +
                    "FROM homeworks hw " +
                    "LEFT OUTER JOIN materials m ON (hw.material_id = m.id) " +
                    "WHERE hw.id = ?1";
            Query q = em.createNativeQuery(sql).setParameter(1, hwId);
            Object[] result = (Object[]) q.getSingleResult();

            if (result != null) {
                dto = new HomeWorkDetailedDTO();
                dto.setHomeWorkDescription(String.valueOf(result[0]));
                dto.setHomeWorkFinishDate(Date.valueOf(String.valueOf(result[1])));
                dto.setFileUrl(String.valueOf(result[2]));
                dto.setHomeWorkMaxScores(Integer.valueOf(String.valueOf(result[3])));
                dto.setHomeWorkStartDate(Date.valueOf(String.valueOf(result[4])));
                dto.setCourseId(Long.valueOf(String.valueOf(result[5])));
                dto.setHomeWorkTitle(String.valueOf(result[6]));

                logger.info("Found result {}", dto);
            }
        } catch (NoResultException e) {
            logger.info("Empty result.");
        } catch (Exception e) {
            logger.error("Can't find HomeWork, got parent Exception with error message {} and \ntrace\n{}", e.getMessage(),
                    e.getStackTrace());
            logger.debug("findHomeworkById() - finished with DAOException.");
            throw new DAOException("Can't find homework. Error message: " + e.getMessage());
        }
        logger.debug("findHomeworkById() - finished.");
        return dto;
    }


    /**
     * Create new course DB record.
     * In case of success id of inserted homework will return. Either null.
     * If sent entity is null, IllegalArgumentException will be thrown.
     * If got some parent exception during persist process, DAOException will be thrown.
     *
     * @param entity                        HomeWorkEntity object.
     * @return                              ID of inserted HomeWork.
     * @throws IllegalArgumentException     If entity is null.
     * @throws DAOException                 If any parent exception appeared.
     */
    public Long create(HomeWorkEntity entity) throws IllegalArgumentException, DAOException {
        logger.debug("create() - started.");

        if (entity == null) {
            logger.error("Persistable entity can't be {}", entity);
            logger.debug("create() - finished with IllegalArgumentException.");
            throw new IllegalArgumentException("Persists entity can't be null.");
        }

        Long lastInsertId = null;
        logger.debug("Adding entity {} to DB.", entity);
        try {
            em.persist(entity);
            em.flush();
            lastInsertId = entity.getId();
            logger.info("Entity successful persists into DB. Entity ID is {}", lastInsertId);
        } catch (Exception e) {
            logger.error("Can't persists entity into DB. Error message: {}, \nTrace:\n {}", e.getMessage(),
                    Arrays.deepToString(e.getStackTrace()));
            logger.debug("create() - finished with DAOException.");
            throw new DAOException("Can't persists entity into DB. Error message: " + e.getMessage());
        }

        logger.debug("create() - finished.");
        return lastInsertId;
    }

    /**
     * Update homework record in DB.
     * In case of success True will be returned, either False.
     * If entity is null, IllegalArgumentException will be thrown.
     * If any parent Exception appeared, DAOException will be thrown.
     *
     * @param entity                        CourseEntity object.
     * @return                              True if record updated successful.
     * @throws IllegalArgumentException     If entity in null.
     * @throws DAOException                 If any parent exception appeared.
     */
    public boolean update(HomeWorkEntity entity) throws IllegalArgumentException, DAOException {
        logger.debug("update() - started.");

        if (entity == null) {
            logger.error("update() Updatable course can't be {}", entity);
            logger.debug("create() - finished with IllegalArgumentException.");
            throw new IllegalArgumentException("Updatable homework can't be null.");
        }

        boolean isUpdated = false;
        logger.debug("update() Update homework {} in DB", entity);
        try {
            em.merge(entity);
            isUpdated = true;
            logger.info("update() Course successful updated.");
        } catch (Exception e) {
            logger.error("update() Can't update courses, message: {}, \nTrace:\n {}", e.getMessage(),
                    Arrays.deepToString(e.getStackTrace()));
            logger.debug("update() - finished with DAOException.");
            throw new DAOException("Can't update course. Error message: " + e.getMessage());
        }

        logger.debug("update() - finished.");
        return isUpdated;
    }

    /**
     * Delete record from DB.
     * In case of success, true will be returned.
     * If entity is null, IllegalArgumentException will be thrown.
     * If any parent Exception appeared, DAOException will be thrown.
     *
     * @param entity                        HomeWorkEntity object.
     * @return                              True if record removed from DB.
     * @throws IllegalArgumentException     If entity is null.
     * @throws DAOException                 If any parent exception appeared.
     */
    public boolean delete(HomeWorkEntity entity) throws DAOException, IllegalArgumentException {
        logger.debug("delete() - started.");
        if (entity == null) {
            logger.error("delete() Deletable course can't be {}", entity);
            logger.debug("delete() - finished with IllegalArgumentException.");
            throw new IllegalArgumentException("Deletable course can't be null.");
        }

        boolean isDeleted = false;
        try {
            HomeWorkEntity homeWorkEntity = em.find(HomeWorkEntity.class,entity.getId());
            em.remove(homeWorkEntity);
            isDeleted = true;
            logger.info("delete() HomeWorkEntity successful deleted from DB.");
        } catch (Exception e) {
            logger.error("delete() Can't delete course from DB. Error message: {}, \nTrace:\n {}", e.getMessage(),
                    Arrays.deepToString(e.getStackTrace()));
            logger.debug("delete() - finished with DAOException.");
            throw new DAOException("Can't delete coruse from DB. Error message: " + e.getMessage());
        }
        logger.debug("delete() - finished.");
        return isDeleted;
    }

}
